#pragma once
#ifndef BUFFER_INTERFACE_HPP
#define BUFFER_INTERFACE_HPP

#include "../Model/FormatSpecify.hpp"
#include "BufferTypeFlags.hpp"

namespace Buffer {

/*
  Single BufferFormat format
*/
struct BufferFormat {
  /*
    Construct a buffer format
    @param bytes The buffer bytes
    @param type The element data type in this buffer
    @param hint buffer format hint
  */
  BufferFormat(size_t bytes, Model::DataType type, GLenum hint)
      : bufferBytes(bytes), dataType(type), hint(hint) {}

  BufferFormat() : bufferBytes(0), dataType(Model::DataType::FLOAT32), hint(0) {}

  ~BufferFormat() { Clear(); }

  void Clear() {
    bufferBytes = 0;
    dataType = Model::DataType::FLOAT32;
    hint = 0;
  }

  size_t bufferBytes;
  Model::DataType dataType;
  GLenum hint;
};

class BufferPool;

/*
  Base buffer interface, include buffer base operation interface in openGL.
  eg. CreateBuffers(), BindBuffer(), MapBuffer(), BufferData(), DeleteBuffer(),
      CopyBuffer(), etc

  the buffer cannot be created external, must create by \class BufferPool
*/
class BufferBase {
public:
  enum BufferAccessState { Error, Success };

  /*
    Bind Current buffer to specified buffer type
    @noreturn
  */
  virtual void BindBuffer();

  /*
    UnBind current buffer target
    @noreturn
  */
  virtual void UnBind();

  /*
    Assign buffer memory data to GL
    @param format buffer format, use by buffer initialized
    @param data concrete data for current buffer
    @param no return
  */
  virtual void AssignData(const BufferFormat &format, const void *data) const;

  /*
    Map server buffer memory addr to client buffer addr.
    @param offset offset from start in the buffer
    @param length mapped buffer range size
  */
  virtual void *SampleBuffer(GLintptr offset, GLsizeiptr length);

  /* Map end */
  virtual BufferAccessState EndSample(GLintptr offset, GLsizeiptr length);

  virtual void *SampleAllBuffer(GLenum access);
  virtual BufferAccessState EndSampleAll();

  /* Get Buffer id used by openGL */
  GLuint GetID() const;

  void SetAccessFlagBits(GLbitfield flags);
  bool Mapped() const;

  /* Delete Current buffer object; */
  virtual void Delete();

  virtual ~BufferBase();

  friend class BufferPool;

protected:
  BufferBase(const BufferType &type);

  /*
      Create Current Buffer
      @param type buffer type
      @noreturn
    */
  virtual void Create();

protected:
  BufferType type;
  GLuint id;
  GLbitfield access_modified;
  bool mapped;
};

} // namespace Buffer

#endif